package com.sojson.util.properties;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;

import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;

import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;

import com.sojson.config.exception.TransException;
import com.sojson.constant.Constant;
import com.sojson.constant.ConstantByMap;
import com.sojson.util.StringUtil;
import com.sojson.util.SystemUtil;
import com.sojson.util.file.enums.EFileType;
import com.sojson.util.properties.impl.PropertiesUtilImpl;

import lombok.extern.slf4j.Slf4j;

/**
 * Properties工具类
 * 
 * @author liu
 * @date 2020-11-02
 */
@Slf4j
public class PropertiesUtil {

    private static final PropertiesUtilInterface PROPERTIES_UTILS_INTERFACE = PropertiesUtilImpl.getInstances();

    // =======================================================================================================
    // ===============================================加载属性文件===============================================
    // =======================================================================================================
    public static final ResourcePatternResolver RESOLVER = new PathMatchingResourcePatternResolver();
    /** 是否根据文件名列表加载环境文件 */
    public static final boolean IS_LOAD_ENVIRONMENT = isLoadEnvironment();
    /** 所有属性文件的路径 */
    public static String ENVIRONMENT = "";
    /** 默认的属性文件的路径 */
    public static String DEFAULT = "";
    /** 会变的属性文件的路径 */
    public static String CHANGE = "";
    /** 属性文件的后缀 */
    private static final String PROPERTIES_SUFFIX = ".properties";
    /** 应用程序配置文件名 */
    private static final String APPLICATION_PROPERTIES = "application" + PROPERTIES_SUFFIX;
    /** 环境文件名前缀 */
    private static final String ENVIRONMENT_FILE_NAME_PREFIX = "application-";
    /** 环境文件名后缀的属性名(要通过这个属性获取到后缀) */
    private static final String SPRING_PROFILES_ACTIVE = "spring.profiles.active";
    /** 多模块下的扩展属性文件的目录 */
    private static final String EXTEND = "extend";
    private static final ScriptEngine SE = new ScriptEngineManager().getEngineByName("JavaScript");
    // =======================================================================================================
    // ================================================替换占位符================================================
    // =======================================================================================================

    /** 以下用来替换占位符 */
    /** 左括号 */
    private static final String DEFAULT_PLACEHOLDER_PREFIX = "${";
    /** 右括号 */
    private static final String DEFAULT_PLACEHOLDER_SUFFIX = "}";
    /** 编码 */
    public static final String DEFAULT_ENCODING = Constant.DEFAULT_ENCODING;
    private static final String REPLACE_PATTERN = "${%s}";

    /** 是否允许空值 */
    public static boolean ALLOW_NULL_VALUE = true;
    /** 是否忽略无法解析的带有通配符的value */
    public static boolean IGNORE_UNRESO_LVABLE_PLACEHOLDERS = false;

    /**
     * 读取resources下所有相同名字的properties文件，返回一个Properites对象
     * 
     * @param fileName properties文件名（文件必须要在resources文件夹下）
     * @return 得到的对象
     * @throws IOException
     */
    public static Resource getResource(String fileName) throws IOException {
        return RESOLVER.getResource(fileName);
    }

    /**
     * 读取resources下所有相同名字的properties文件，返回一个Properites对象
     * 
     * @param filePath 文件路径
     * @return 得到的对象
     * @throws IOException
     */
    public static List<Resource> getResources(String filePath) throws IOException {
        List<Resource> resources = new ArrayList<>();
        String[] split = filePath.split(",");
        for (String mapperXmlLocation : split) {
            Resource[] resourceArr = RESOLVER.getResources(mapperXmlLocation);
            resources.addAll(Arrays.asList(resourceArr));
        }
        return resources;
    }

    /**
     * 读取resources下所有相同名字的properties文件，返回一个Properites对象
     * 
     * @param filePath 文件路径
     * @return 得到的对象
     * @throws IOException
     */
    public static Resource[] getResourceArr(String filePath) throws IOException {
        List<Resource> resources = getResources(filePath);
        return resources.toArray(new Resource[resources.size()]);
    }

    /**
     * 读取resources下所有相同名字的properties文件，返回一个Properites对象
     * 
     * @param fileName properties文件名（文件必须要在resources文件夹下）
     * @return 得到的对象
     * @throws IOException
     */
    public static Properties getProperties(String fileName) throws IOException {
        return PROPERTIES_UTILS_INTERFACE.getProperties(fileName);
    }

    /**
     * 读取resources下所有相同名字的properties文件，返回一个Properites对象
     * 
     * @param fileName  properties文件名（文件必须要在resources文件夹下）
     * @param encoding  字符编码
     * @return 得到的对象
     * @throws IOException
     */
    public static Properties getProperties(String fileName, String encoding) throws IOException {
        return PROPERTIES_UTILS_INTERFACE.getProperties(fileName, encoding);
    }

    /**
     * 根据properties文件输入流获取Properties对象
     * 
     * @param file 文件
     * @return 得到的Properties对象
     * @throws IOException
     */
    public static Properties getProperties(File file) throws IOException {
        return PROPERTIES_UTILS_INTERFACE.getProperties(file);
    }

    /**
     * 根据properties文件输入流获取Properties对象
     * 
     * @param file 文件
     * @param encoding  字符编码
     * @return 得到的Properties对象
     * @throws IOException
     */
    public static Properties getProperties(File file, String encoding) throws IOException {
        return PROPERTIES_UTILS_INTERFACE.getProperties(file, encoding);
    }

    /**
     * 根据properties文件路径获取Properties对象
     * 
     * @param resource
     * @return 得到的Properties对象
     * @throws IOException
     */
    public static Properties getProperties(Resource resource) throws IOException {
        return PROPERTIES_UTILS_INTERFACE.getProperties(resource);
    }

    /**
     * 根据properties文件路径获取Properties对象
     * 
     * @param resource
     * @param encoding  字符编码
     * @return 得到的Properties对象
     * @throws IOException
     */
    public static Properties getProperties(Resource resource, String encoding) throws IOException {
        return PROPERTIES_UTILS_INTERFACE.getProperties(resource, encoding);
    }

    /**
     * 根据properties文件路径获取Properties对象
     * 
     * @param inputStream properties文件输入流
     * @return 得到的Properties对象
     * @throws IOException
     */
    public static Properties getProperties(InputStream inputStream) throws IOException {
        return PROPERTIES_UTILS_INTERFACE.getProperties(inputStream);
    }

    /**
     * 将带符号的字符串转换为数字
     * 
     * @param value
     * @return
     */
    public static Object getNumberFormat(String value) {
        try {
            if (StringUtil.isBlankObject(value)) {
                return value;
            }
            return SE.eval(value);
        } catch (ScriptException e) {
            throw new TransException();
        }
    }

    /**
     * 获取properites文件中的某个值
     * 
     * @param fileName properties文件名（文件必须要在resources文件夹下）
     * @param key 要获取的值的key
     * @return 得到的值
     * @throws IOException
     */
    public static String getValue(String fileName, String key) throws IOException {
        return PROPERTIES_UTILS_INTERFACE.getValue(fileName, key);
    }

    /**
     * 是否根据文件名列表加载环境文件
     * 
     * @return
     */
    private static boolean isLoadEnvironment() {
        return false;
    }

    /**
     * 将Properties文件属性加载到程序中(Constant.PROPERTIES中)
     * 
     * @param list
     * @throws IOException
     */
    public static void loadToSystem() {
        try {
            // 属性文件的位置
            String propertiesFilePath = null;
            // 默认的文件的位置
            String propertiesConstantFilePath = null;
            // 会变的文件的位置
            String propertiesChangeFilePath = null;

            // application文件
            Properties application = null;
            // 所有属性文件的目录的key
            String environmentProperties = "so.environment.properties";
            // 默认的属性文件的目录的key
            String environmentDefaultProperties = "so.environment.properties.default";
            // 会变的属性文件的目录的key
            String environmentChangeProperties = "so.environment.properties.change";

            // 读取application.properties文件
            ClassPathResource classPathResource = new ClassPathResource(APPLICATION_PROPERTIES);
            if (classPathResource.exists()) {
                application = PropertiesUtil.getProperties(APPLICATION_PROPERTIES);
                String springProfilesActive = application.getProperty(SPRING_PROFILES_ACTIVE);
                // 如果设置了环境文件就加载这个文件
                if (StringUtil.isNotBlankObject(springProfilesActive)) {
                    String[] split = springProfilesActive.split(",");
                    for (String string : split) {
                        // 环境文件名
                        String environmentName = new StringBuilder(ENVIRONMENT_FILE_NAME_PREFIX).append(string)
                            .append(".").append(Constant.PROPERTIES_SUFFIX_NAME).toString();
                        classPathResource = new ClassPathResource(environmentName);
                        if (classPathResource.exists()) {
                            // 读取application-${spring.profiles.active}.properties文件
                            application.putAll(PropertiesUtil.getProperties(environmentName));
                        }
                    }
                }
            } else {
                application = new Properties();
            }

            String x = "/**/*" + PROPERTIES_SUFFIX;
            // /environment
            ENVIRONMENT = application.getProperty(environmentProperties);
            propertiesFilePath = ENVIRONMENT;
            // /default
            DEFAULT = application.getProperty(environmentDefaultProperties);
            // /environment/default
            propertiesConstantFilePath = propertiesFilePath + DEFAULT;
            // /???-???
            CHANGE = application.getProperty(environmentChangeProperties);
            // /environment/???-???
            propertiesChangeFilePath = propertiesFilePath + CHANGE;

            // 默认的属性文件的目录
            classPathResource = new ClassPathResource(propertiesConstantFilePath);
            if (classPathResource.exists()) {
                List<Resource> resources = getResources(ConstantByMap.CLASSPATHX_ + propertiesConstantFilePath + x);
                for (Resource resource : resources) {
                    application.putAll(getProperties(resource));
                }
            }
            classPathResource = new ClassPathResource(propertiesChangeFilePath);
            if (classPathResource.exists()) {
                List<Resource> resources = getResources(ConstantByMap.CLASSPATHX_ + propertiesChangeFilePath + x);
                for (Resource resource : resources) {
                    application.putAll(getProperties(resource));
                }
            }
            classPathResource = new ClassPathResource(EXTEND);
            if (classPathResource.exists()) {
                List<Resource> resources = getResources(ConstantByMap.CLASSPATHX_ + EXTEND + x);
                for (Resource resource : resources) {
                    application.putAll(getProperties(resource));
                }
            }

            // 整合所有Properties属性
            putAll(application);
            log.info("Properties属性文件属性已加载到Constant.PROPERTIES中！");
            replacePlaceholder(Constant.PROPERTIES);
        } catch (IOException e) {
            log.info("Properties属性文件属性加载到Constant.PROPERTIES中失败！");
            e.printStackTrace();
            exit();
        }
    }

    /**
     * 读取Properties属性文件
     * 
     * @param application
     * @param file
     * @param path
     * @throws IOException 
     */
    @SuppressWarnings("unused")
    private static void getProperties(Properties application, File file, String path) throws IOException {
        if (file.exists()) {
            if (file.isFile()) {
                String suffix = EFileType.PROPERTIES.getNameD();
                if (file.getName().endsWith(suffix)) {
                    application.putAll(getProperties(path));
                }
            } else {
                File[] listFiles = file.listFiles();
                for (File f : listFiles) {
                    getProperties(application, f, path + "/" + f.getName());
                }
            }
        }
    }

    /**
     * 替换占位符
     * 
     * @param properties
     */
    public static void replacePlaceholder(Properties properties) {
        // 标记是否有占位符
        boolean isContinue = true;
        while (isContinue) {
            isContinue = false;
            // 替换占位符(Spring没有替换占位符,@Value注解是经过特殊处理的)
            Set<Entry<Object, Object>> entrySet = properties.entrySet();
            Iterator<Entry<Object, Object>> iterator = entrySet.iterator();
            while (iterator.hasNext()) {
                Entry<Object, Object> next = iterator.next();
                String key = (String)next.getKey();
                String value = (String)next.getValue();
                // 把不需要使用的属性去除掉
                if (key.startsWith("so.no.")) {
                    iterator.remove();
                    continue;
                }
                // 解决Properties相互引用问题
                String resolveValueReferences = resolveValueReferences(properties, key, value);
                if (!isContinue && isNotBlankObject(value) && !value.equals(resolveValueReferences)) {
                    isContinue = true;
                }
                properties.put(key, resolveValueReferences);
            }
        }
        log.info("Properties文件属性已替换占位符！");

        // 把特定属性放到指定集合中,有些需要单独区分方便操作
        for (Object key : properties.keySet()) {
            String keyStr = (String)key;
            String value = properties.getProperty(keyStr);
            // 查找前缀为so.的属性
            if (keyStr.startsWith("so.log.directory.") || keyStr.startsWith("so.no.log.directory.")) {
                // 把前缀为so.log.或者为so.no.log.的属性单独放到一个Map集合中,这是要创建的目录,遍历的时候好遍历
                Constant.LOG_PATH.put(keyStr, value);
            }
        }
    }

    /**
     * 更新value，如果是确定的值，直接返回
     * 如果不是确定的值，则进行替换，
     * 先渠道${key}的key,然后在旧的Properties中查找并通过替换到原来的整个value，
     * 此时判断替换后的value和旧的value是否相等
     * 如果相等，则是循环引用或替换失败
     * 如果含表达式${key},则继续解析
     * @param props
     * @param val
     */
    private static String resolveValueReferences(Properties props, String key, String val) {
        // 正常的值，即没有通配符 ${}
        if (!isNormalValue(key, val)) {
            return val;
        }
        int i = val.indexOf(DEFAULT_PLACEHOLDER_PREFIX);
        int j = val.indexOf(DEFAULT_PLACEHOLDER_SUFFIX);
        // 截取子表达式，如：${zookeeper}:${port}/${addr}
        String subKey;
        String subValue;
        StringBuilder sb = new StringBuilder();
        // 先拼接前面一段没有占位符的字符串
        if (i > -1 && j > -1 && i < val.length() && j < val.length() && i < j) {
            sb.append(val.substring(0, i));
        }
        // 拼接剩余字符串
        while (i > -1 && j > -1 && i < val.length() && j < val.length() && i < j) {
            subKey = val.substring(i + 2, j);
            subValue = props.getProperty(subKey);
            if (subValue == null) {
                if (IGNORE_UNRESO_LVABLE_PLACEHOLDERS) {
                    log.info(String.format("找不到key为%s的配置项，保留通配符。", subKey));
                    sb.append(String.format(REPLACE_PATTERN, subKey));
                } else {
                    log.info(String.format("找不到key为%s的配置项，不保留通配符。", subKey));
                }
            } else {
                sb.append(subValue);
            }
            i = val.indexOf(DEFAULT_PLACEHOLDER_PREFIX, j);
            /**
             * 1.没有通配符
             * 2.剩下还有通配符，则截取后继续解析
             */
            if (i == -1) {
                sb.append(val.substring(j + 1));
            } else if (i > j + 1) {
                // 拼接余下的字符串
                sb.append(val.substring(j + 1, i));
            }
            j = val.indexOf(DEFAULT_PLACEHOLDER_SUFFIX, i);
        }
        return sb.toString();
    }

    /**
     * 检查value是否含有表达式
     * @param val
     * @return
     */
    private static boolean isNormalValue(String key, String val) {
        if (!ALLOW_NULL_VALUE && val == null) {
            throw new RuntimeException(String.format("Properties Item 不允许为空！当前 key :%s", key));
        }
        if (val != null) {
            int i = val.indexOf(DEFAULT_PLACEHOLDER_PREFIX);
            int j = val.indexOf(DEFAULT_PLACEHOLDER_SUFFIX);
            if (i >= 0 && j > 0 && j != i) {
                return true;
            }
        }
        return false;
    }

    /**
     * 判断一个对象是否不是空(空对象)
     * 
     * @param obj
     * @return
     */
    private static boolean isNotBlankObject(Object obj) {
        return StringUtil.isNotBlankObject(obj);
    }

    /**
     * 整合Properties属性
     */
    private static void putAll(Properties properties) {
        if (isNotBlankObject(properties)) {
            Constant.PROPERTIES.putAll(properties);
        }
    }

    /**
     * 退出系统
     */
    private static void exit() {
        SystemUtil.exit();
    }

}